知乎上有人问了一个问题:Python有哪些新手不会了解的深入细节。 其中的一个答案引用了stackoverflow上的一个问题解答。 鉴于一直在努力摆脱Python小白,决定好好研究下这几个特性,顺手翻译一下下,扩展一下下~~
Argument Unpacking
可以使用*和**分别将一个列表或一个字典解包为函数参数。 例如: 1
2
3
4
5
6
7
8def draw_point(x, y):
# do some magic
point_foo = (3, 4)
point_bar = {'y': 3, 'x': 2}
draw_point(*point_foo)
draw_point(**point_bar)
Braces
假如你不喜欢用空格来表示范围,那么可以通过下面的方式来使用C风格的{} 1
from __future__ import braces
SyntaxError: not a chance (<pyshell#1>, line 2)
这说明,这个特性永远不可能实现的。 Python开发者超有幽默感的,你可以试试输入例如import __hello__
, import this
, import antigravity
看看会发生啥事~~
Chaining Comparison Operators
1 | 5 x = |
如果你是这样想的:首先1 < x
,结果是True
,然后比较True < 10
,结果也是True
。那么,不好意思,你错了哦,事实根本不是这样的好不好。它会转换为1 < x and x < 10
,和x < 10 and 10 < x * 10 and x*10 < 100
。当然,这样就不需要输入那么多东东,而且每一个语句只会被计算一次。
碎碎念
Lisp貌似也是这样玩的。 然后,上面最后例子为嘛是False呢?其实你可以理解为5 in [5] and [5] is True
,这样,就知道为嘛了~~
Decorators
装饰器允许在另一个函数中包装一个函数或方法,这样可以增加功能,修改参数或结果等。在函数定义的上面一行写上装饰器,以"at"(@)标志开头。 下面的例子展示了一个print_args
装饰器,它在调用函数前打印所装饰的函数参数: 1
2
3
4
5
6
7
8
9
10
11
12
13def print_args(function):
def wrapper(*args, **kwargs):
print 'Arguments:', args, kwargs
return function(*args, **kwargs)
return wrapper
@print_args
def write(text):
print text
'foo') write(
Arguments: ('foo',) {}
foo
Default Argument Gotchas / Dangers of Mutable Default arguments
要小心可变的默认参数哦~~ 1
2
3
4
5
6
7
8
9
10def foo(x=[]):
1) x.append(
print x
foo()
[1]
foo()
[1, 1]
foo()
[1, 1, 1]1
2
3
4
5
6
7
8
9def foo(x=None):
if x is None:
x = []
1) x.append(
print x
foo()
[1]
foo()
[1]
另外,上面第二个例子可以改写为 1
2
3
4def foo(x=None):
x = x or [] # 或者 x = [] if x is None
x.append(1)
print x1
2
3
4
5
6
7
810 x =
lambda y:x+y a =
20 x =
lambda y:x+y b =
10) a(
30
10) b(
301
2
3
4
5
6
7
810 x =
lambda y, x=x:x+y a =
20 x =
lambda y, x=x:x+y b =
10) a(
20
10) b(
30
Descriptors
他们是一大堆Python核心特性后面的魔法~~ 当你使用"."访问的形式来查找一个成员(例如,x.y
),Python首先查找实例字典中的成员。如果找不到,则在类字典中查找。如果在类字典中找到了,而对象实现了描述符(descriptor)协议,Python会执行它,而不是仅仅返回它。一个描述符是任何实现__get__
, __set__
或者__delete__
方法的类。 下面是如何使用描述符实现你自己的属性的(只读)版本: 1
2
3
4
5
6
7
8class Property(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, type):
if obj is None:
return self
return self.fget(obj)property()
属性一样使用它: 1
2
3
4class MyClass(object):
def foo(self):
return "Foo!"
碎碎念
装饰器和描述符是两个不一样的东东哦~ 扩展阅读: * Python descriptor * Python描述符(descriptor)解密
Dictionary default .get value
字典有一个get()
方法。若你使用d['key']
而key不存在,那么你将会得到一个异常。而如果使用d.get('key')
,那么当key不存在时,会返回None。当然.get()
方法提供给了第二个参数,若此参数设了值,那么当key不存在的时候,会返回你所制定的值。例如,d.get('key',0)
,当d['key']不存在时,返回0. 这对某些操作很有用,列入,给数字做加法: 1
sum[value] = sum.get(value, 0) + 1
Docstring Tests
Doctest: 文档和单元测试同时进行 从Python文档中抽取的例子: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
If the result is small enough to fit in an int, return an int.
Else return a long.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
def _test():
import doctest
doctest.testmod()
if __name__ == "__main__":
_test()
Ellipsis Slicing Syntax
Python高级切片操作有一个罕为人知的语法元素:省略(Ellipsis): 1
2
3
4
5
6class C(object):
def __getitem__(self, item):
return item
1:2, ..., 3] C()[
(slice(1, 2, None), Ellipsis, 3)
碎碎念
这个特性真是令人困惑呢~~ 一般会出现在numpy或者scipy模块中,几个例子体会一下: 1
2
3
4
5
6
7
8
9
10from numpy import *
1,2,3],[3,4,5],[5,6,7]]) a = array([[
print a
[[1 2 3]
[3 4 5]
[5 6 7]]
print a[...,0]
[1 3 5]
print a[0,...,0] # a[0,...,0] == a[0][0]
1__getitem__
了
Enumeration
用enumerate包住一个可迭代对象,它将会将index和item绑在一起,返回一个enumerate对象。 例如: 1
2
3
4
5
6
7
8
9'a', 'b', 'c', 'd', 'e'] a = [
for index, item in enumerate(a): print index, item
...
0 a
1 b
2 c
3 d
4 e
>>>
碎碎念
这个当同时需要索引和值的时候灰常有用。 比如说,当你想这样做的时候: 1
for i in range(len(a)): print i, a[i]
enumereate(a,1)
For/else
for...else语法如下: 1
2
3
4
5for i in foo:
if i == 0:
break
else:
print("i was never 0")1
2
3
4
5
6
7found = False
for i in foo:
if i == 0:
found = True
break
if not found:
print("i was never 0")
上面的代码也等价于 1
2
3
4
5found = False
if any(i == 0 for i in foo):
pass
else:
print("i was never 0")
Function as iter() argument
iter()接收一个可回调参数。 例如: 1
2
3def seek_next_line(f):
for c in iter(lambda: f.read(1),'\n'):
pass1
2
3
4
5
6
7
8
9
10
11
### 碎碎念
函数说明:
```python
iter(...)
iter(collection) -> iterator
iter(callable, sentinel) -> iterator
Get an iterator from an object. In the first form, the argument must
supply its own iterator, or be a sequence.
In the second form, the callable is called until it returns the sentinel.
Generator expressions
假如你像下面这样写: 1
x=(n for n in foo if bar(n))
1
for n in x:
1
2x = [n for n in foo if bar(n)]
#列表推导1
2
3
4
5
6
7
8for a in range(0,2) for b in range(4,6)) n = ((a,b)
for i in n:
print i
(0, 4)
(0, 5)
(1, 4)
(1, 5)
import this
1 | import this |
结果是(翻译见The Zen of Python): The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
碎碎念
前面from __future__ ipmort braces
提到了这个import this
。现在就让我们来看看会发生神马吧。 从这个模块的源代码来看(位于python目录下的Lib/this.py),这个是对一段加密后的s进行解密操作(ROT13),然后输出。
从源码来看,这个彩蛋是很令人困惑的。至于为什么这样写,有人说,只是个玩笑,因为这个源码本身就违反了Zen of Python。你觉得呢?
In Place Value Swapping
1 | 10 a = |
等号右边是一个表达式,它创建了一个新的元组。等号左边立即将这个未引用的元组拆封赋值给a和b 赋值后,新的元组属于未被引用,并标记为可回收垃圾,而a和b绑定的值则被互换。 注意在Python tutorial section on data structures中有, 注意,多重赋值其实只是元组封装和序列拆封的组合。
碎碎念
此时,有小伙伴提出疑问,这会不会被传统的方式使用更多的内存空间呢? 答案是,不会!